﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Marmat.Forms.Skin
{
    /// <summary>
    /// 布局视图控件。
    /// </summary>
    public class LayoutView : Marmat.Forms.Skin.ScrollControl
    {
        /// <summary>
        /// 初始化 Marmat.Forms.Skin.LayoutView 类的新实例。
        /// </summary>
        public LayoutView()
        {
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.ResizeRedraw, true); //尺寸变化后绘制
            SetStyle(ControlStyles.Opaque, false); //改善绘图质量
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.DoubleBuffer, true);
            this.Items = new ObjectCollection(this);
        }

        private readonly Dictionary<int, Rectangle> item_rect = new Dictionary<int, Rectangle>();
        private int selectedIndex = -1;
        private int mousemoveIndex = -1;

        /// <summary>
        /// 行间距
        /// </summary>
        [DefaultValue(0)]
        public int RowSpacing { get; set; } = 0;

        /// <summary>
        /// 列间距
        /// </summary>
        [DefaultValue(0)]
        public int ColumnSpacing { get; set; } = 0;


        /// <summary>
        /// 项的高度
        /// </summary>
        [DefaultValue(typeof(Size), "40,20")]
        public Size ItemSize { get; set; } = new Size(40, 20);

        /// <summary>
        /// 获取 Marmat.Forms.Skin.LayoutView 的项。
        /// </summary>
        [Localizable(true), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public virtual ObjectCollection Items { get; }

        /// <summary>
        /// 在所有者绘制的 Marmat.Forms.Skin.LayoutView 在视觉外观更改时发生。
        /// </summary>
        public event DrawItemEventHandler DrawItem;

        /// <summary>
        /// 创建所有者描述的 Marmat.Forms.Skin.LayoutView 和确定列表项的大小时发生。
        /// </summary>
        public event MeasureItemEventHandler MeasureItem;

        /// <summary>
        /// 选中SelectedIndex 属性更改后发生。
        /// </summary>
        public event EventHandler SelectedIndexChanged;

        /// <summary>
        /// 点击项时触发的事件。
        /// </summary>
        public event EventHandler<ItemEventArgs> ItemClick;

        /// <summary>
        /// 双击项时触发的事件。
        /// </summary>
        public event EventHandler<ItemEventArgs> ItemDoubleClick;

        /// <summary>
        /// 获取或设置当前选中项。
        /// </summary>
        [Browsable(false)]
        [DefaultValue(null)]
        public object SelectedItem
        {
            get
            {
                if (this.Items.Count > 0 && this.selectedIndex >= 0)
                    return this.Items[this.selectedIndex];
                else
                    return null;
            }
            set
            {
                if (value is null)
                    this.SelectedIndex = -1;
                else
                {
                    var index = this.Items.FindIndex(x => x == value);
                    if (index >= 0)
                        this.SelectedIndex = index;
                    else
                        throw new ArgumentException("当前赋予的值不在集合中。");
                }
            }
        }

        /// <summary>
        /// 获取或设置 Marmat.Forms.Skin.LayoutView 中当前选定项的从零开始的索引。       
        /// </summary>
        /// <remarks>当前选定项的从零开始的索引。 如果未选定任何项，则返回值为负一 (-1)。</remarks>
        [DefaultValue(-1)]
        public int SelectedIndex
        {
            get => selectedIndex;
            set
            {
                if (selectedIndex == value)
                    return;
                selectedIndex = value;
                this.SelectedIndexChanged?.Invoke(this, EventArgs.Empty);
                this.Invalidate();
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            item_rect.Clear();
            base.OnPaint(e);
            int top = this.Padding.Top;
            int left = this.Padding.Left;
            int scroll = this.VerticalScroll.Value;
            int width = this.Width - this.Padding.Left - this.Padding.Right;
            int columIndex = 0;
            int rowIndex = 0;
            int itemHeight = 0;
            for (int i = 0; i < this.Items.Count; i++)
            {
                object item = this.Items[i];
                var itemSize = this.ItemSize;
                if (this.MeasureItem != null)
                {
                    MeasureItemEventArgs args = new MeasureItemEventArgs(e.Graphics, i);
                    args.ItemWidth = itemSize.Width;
                    args.ItemHeight = itemSize.Height;
                    this.MeasureItem.Invoke(this, args);
                    itemSize = new Size(args.ItemHeight, args.ItemWidth);
                }
                if (columIndex > 0 && left + itemSize.Width > width)
                {
                    rowIndex++;
                    columIndex = 0;
                    top += itemHeight + this.RowSpacing;
                    left = this.Padding.Left;
                    itemHeight = 0;
                }
                columIndex++;
                itemHeight = Math.Max(itemHeight, itemSize.Height);

                int bottom = top + itemHeight;
                if (bottom > scroll && top < scroll + this.Height)
                {
                    Rectangle item_rectangle = new Rectangle(left, top - scroll, itemSize.Width, itemSize.Height);
                    DrawItemState state = DrawItemState.None;
                    Color foreColor = this.ForeColor;
                    Color backColor = this.BackColor;
                    if (this.SelectedIndex == i)
                    {
                        state |= DrawItemState.Selected;
                        foreColor = Color.White;
                    }
                    if (this.mousemoveIndex == i)
                        state |= DrawItemState.HotLight;
                    else
                        state |= DrawItemState.Default;
                    DrawItemEventArgs args = new DrawItemEventArgs(e.Graphics, this.Font, item_rectangle, i, state, foreColor, backColor);
                    if (this.DrawItem is null)
                    {
                        args.DrawBackground();
                        string display = item?.ToString();
                        TextRenderer.DrawText(e.Graphics, display, this.Font, item_rectangle, foreColor, Color.Transparent, TextFormatFlags.VerticalCenter);
                    }
                    else
                    {
                        this.DrawItem.Invoke(this, args);
                    }
                    item_rect[i] = item_rectangle;
                }
                left += itemSize.Width + this.ColumnSpacing;
            }
            top += itemHeight + 1 + this.Padding.Bottom;
            this.AutoScrollMinSize = new Size(0, top);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            var collection = this.item_rect.ToList();
            if (collection.Exists(x => x.Value.Contains(e.Location)))
            {
                var item = this.item_rect.First(x => x.Value.Contains(e.Location));
                if (item.Key != this.mousemoveIndex)
                {
                    this.mousemoveIndex = item.Key;
                    this.Invalidate();
                }
            }
            else if (this.mousemoveIndex != -1)
            {
                this.mousemoveIndex = -1;
                this.Invalidate();
            }
        }
        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            if (this.mousemoveIndex != -1)
            {
                this.mousemoveIndex = -1;
                this.Invalidate();
            }
        }

        protected override void OnMouseClick(MouseEventArgs e)
        {
            base.OnMouseClick(e);
            if (this.Items.Count > 0)
            {
                var collection = this.item_rect.ToList();
                if (collection.Exists(x => x.Value.Contains(e.Location)))
                {
                    this.SelectedIndex = collection.FirstOrDefault(x => x.Value.Contains(e.Location)).Key;
                    var args = new ItemEventArgs(this.Items[this.selectedIndex]);
                    this.ItemClick?.Invoke(this, args);
                }
            }
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);
            if (this.Items.Count > 0)
            {
                var collection = this.item_rect.ToList();
                if (collection.Exists(x => x.Value.Contains(e.Location)))
                {
                    this.SelectedIndex = collection.FirstOrDefault(x => x.Value.Contains(e.Location)).Key;
                    var args = new ItemEventArgs(this.Items[this.selectedIndex]);
                    this.ItemDoubleClick?.Invoke(this, args);
                }
            }
        }

        protected override void OnScroll(ScrollEventArgs se)
        {
            base.OnScroll(se);
            this.Invalidate();
        }
    }
}
